/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ART OWL API.
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2009.
 * All Rights Reserved.
 *
 * The ART OWL API were developed by the Artificial Intelligence Research Group
 * (art.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about the ART OWL API can be obtained at 
 * http://art.uniroma2.it/owlart
 *
 */

package it.uniroma2.art.owlart.models.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URL;
import java.util.Map;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.exceptions.QueryEvaluationException;
import it.uniroma2.art.owlart.exceptions.UnsupportedQueryLanguageException;
import it.uniroma2.art.owlart.exceptions.UnsupportedRDFFormatException;
import it.uniroma2.art.owlart.io.RDFFormat;
import it.uniroma2.art.owlart.model.ARTBNode;
import it.uniroma2.art.owlart.model.ARTLiteral;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTNodeFactory;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTStatement;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.model.impl.ARTNodeFactoryImpl;
import it.uniroma2.art.owlart.models.BaseRDFTripleModel;
import it.uniroma2.art.owlart.models.TripleQueryModelHTTPConnection;
import it.uniroma2.art.owlart.navigation.ARTNamespaceIterator;
import it.uniroma2.art.owlart.navigation.ARTResourceIterator;
import it.uniroma2.art.owlart.navigation.ARTStatementIterator;
import it.uniroma2.art.owlart.navigation.RDFIterator;
import it.uniroma2.art.owlart.query.BooleanQuery;
import it.uniroma2.art.owlart.query.GraphQuery;
import it.uniroma2.art.owlart.query.MalformedQueryException;
import it.uniroma2.art.owlart.query.Query;
import it.uniroma2.art.owlart.query.QueryLanguage;
import it.uniroma2.art.owlart.query.TupleBindings;
import it.uniroma2.art.owlart.query.TupleBindingsIterator;
import it.uniroma2.art.owlart.query.TupleQuery;
import it.uniroma2.art.owlart.query.Update;

public class SPARQLBasedRDFTripleModelImpl extends BaseRDFModelImpl implements BaseRDFTripleModel {

	private final static String readOnlyErrorMsg = "TripleQuery based models, such as those based on SPARQL access to remote triple stores, are obviously read-only and thus do not allow this method to be used";

	private TripleQueryModelHTTPConnection tqModel;
	private ARTNodeFactory nodeFact;

	public SPARQLBasedRDFTripleModelImpl(TripleQueryModelHTTPConnection tqModel) {
		this.tqModel = tqModel;
		nodeFact = new ARTNodeFactoryImpl();
	}

	public Query createQuery(QueryLanguage ql, String query, String baseURI)
			throws UnsupportedQueryLanguageException, ModelAccessException, MalformedQueryException {
		return tqModel.createQuery(ql, query, baseURI);
	}

	public BooleanQuery createBooleanQuery(QueryLanguage ql, String query, String baseURI)
			throws UnsupportedQueryLanguageException, ModelAccessException, MalformedQueryException {
		return tqModel.createBooleanQuery(ql, query, baseURI);
	}

	public GraphQuery createGraphQuery(QueryLanguage ql, String query, String baseURI)
			throws UnsupportedQueryLanguageException, ModelAccessException, MalformedQueryException {
		return tqModel.createGraphQuery(ql, query, baseURI);
	}

	public TupleQuery createTupleQuery(QueryLanguage ql, String query, String baseURI)
			throws UnsupportedQueryLanguageException, ModelAccessException, MalformedQueryException {
		return tqModel.createTupleQuery(ql, query, baseURI);
	}

	// ******************************
	// EXCEPTIONS ON WRITE ATTEMPTS
	// ******************************

	public void addTriple(ARTResource subject, ARTURIResource predicate, ARTNode object,
			ARTResource... graphs) throws ModelUpdateException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void addStatement(ARTStatement stat, ARTResource... graphs) throws ModelUpdateException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void deleteTriple(ARTResource subject, ARTURIResource property, ARTNode object,
			ARTResource... graphs) throws ModelUpdateException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void deleteStatement(ARTStatement statement, ARTResource... graphs) throws ModelUpdateException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void clearRDF(ARTResource... graphs) throws ModelUpdateException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void addRDF(File inputFile, String baseURI, RDFFormat rdfFormat, ARTResource... graphs)
			throws FileNotFoundException, IOException, ModelAccessException, ModelUpdateException,
			UnsupportedRDFFormatException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	public void addRDF(URL url, String baseURI, RDFFormat rdfFormat, ARTResource... graphs)
			throws FileNotFoundException, IOException, ModelAccessException, ModelUpdateException,
			UnsupportedRDFFormatException {
		throw new ModelUpdateException(readOnlyErrorMsg);
	}

	// *********************************
	// METHODS NOT CURRENTLY SUPPORTED
	// *********************************

	public void writeRDF(File outputFile, RDFFormat rdfFormat, ARTResource... graphs) throws IOException,
			ModelAccessException, UnsupportedRDFFormatException {
		throw new ModelAccessException(
				"serializing an RDF file from a remote TripleQuery access point is not currently supported");

	}

	public void writeRDF(OutputStream os, RDFFormat rdfFormat, ARTResource... graphs) throws IOException,
			ModelAccessException, UnsupportedRDFFormatException {
		throw new ModelAccessException(
				"serializing an RDF file from a remote TripleQuery access point is not currently supported");
	}
	
	public void writeRDF(RDFIterator<ARTStatement> it, RDFFormat format, OutputStream out)
			throws ModelAccessException, UnsupportedRDFFormatException, IOException {
		baseRep.writeRDF(it, format, out);		
	}

	public void writeRDF(RDFIterator<ARTStatement> it, RDFFormat format, Writer wout)
			throws ModelAccessException, UnsupportedRDFFormatException, IOException {
		baseRep.writeRDF(it, format, wout);
	}

	public ARTNamespaceIterator listNamespaces() throws ModelAccessException {
		throw new ModelAccessException(
				"this method is currently not supported on repository accessed via SPARQL");
	}

	public ARTResourceIterator listNamedGraphs() throws ModelAccessException {
		throw new ModelAccessException(
				"this method is currently not supported on repository accessed via SPARQL");
	}

	// *********************************
	// RDFBaseMethods remapped over SPARQL
	// *********************************

	public boolean hasTriple(ARTResource subj, ARTURIResource pred, ARTNode obj, boolean inferred,
			ARTResource... graphs) throws ModelAccessException {
		String query = askQueryHeader + prepareTripleAsGraphPattern(subj, pred, obj);
		try {
			BooleanQuery bq = tqModel.createBooleanQuery(QueryLanguage.SPARQL, query, getBaseURI());
			return bq.evaluate(inferred);
		} catch (UnsupportedQueryLanguageException e) {
			throw new ModelAccessException(e);
		} catch (MalformedQueryException e) {
			throw new ModelAccessException(e);
		} catch (QueryEvaluationException e) {
			throw new ModelAccessException(e);
		}
	}

	public boolean hasStatement(ARTStatement stat, boolean inferred, ARTResource... graphs)
			throws ModelAccessException {
		return hasTriple(stat.getSubject(), stat.getPredicate(), stat.getObject(), inferred, graphs);
	}

	// qui devo fare una wrapper class (inner a questa) per ARTStatementIterator
	// che poi in realtà gli basterà iterare sui TupleBindings a poi wrappare Statement
	public ARTStatementIterator listStatements(ARTResource subj, ARTURIResource pred, ARTNode obj,
			boolean inferred, ARTResource... graphs) throws ModelAccessException {
		//System.out.println("listStatements");
		String query = selectQueryHeader + prepareTripleAsGraphPattern(subj, pred, obj);
		//System.out.println("query: " + query);
		try {
			TupleQuery tq = tqModel.createTupleQuery(QueryLanguage.SPARQL, query, getBaseURI());
			TupleBindingsIterator bit = tq.evaluate(inferred);
			return new StatementIteratorAfterTupleBindingsIterator(bit, subj, pred, obj);
		} catch (UnsupportedQueryLanguageException e) {
			throw new ModelAccessException(e);
		} catch (MalformedQueryException e) {
			throw new ModelAccessException(e);
		} catch (QueryEvaluationException e) {
			throw new ModelAccessException(e);
		}
	}

	public void close() throws ModelUpdateException {
		try {
			tqModel.disconnect();
		} catch (ModelAccessException e) {
			throw new ModelUpdateException(e);
		}

	}

	public void setDefaultNamespace(String namespace) throws ModelUpdateException {
		// TODO Auto-generated method stub
	}

	public void setBaseURI(String uri) throws ModelUpdateException {
		// TODO Auto-generated method stub
	}

	public String getDefaultNamespace() {
		// TODO Auto-generated method stub
		return null;
	}

	public String getBaseURI() {
		// TODO Auto-generated method stub
		return null;
	}

	// ****************************
	// * RDF NODE FACTORY METHODS *
	// ****************************

	public ARTLiteral createLiteral(String literalString) {
		return nodeFact.createLiteral(literalString);
	}

	public ARTLiteral createLiteral(String literalString, String language) {
		return nodeFact.createLiteral(literalString, language);
	}

	public ARTLiteral createLiteral(String literalString, ARTURIResource datatype) {
		return nodeFact.createLiteral(literalString, datatype);
	}

	public ARTURIResource createURIResource(String uri) {
		return nodeFact.createURIResource(uri);
	}

	public ARTBNode createBNode() {
		return nodeFact.createBNode();
	}

	public ARTBNode createBNode(String ID) {
		return nodeFact.createBNode(ID);
	}
	
	public ARTStatement createStatement(ARTResource subject, ARTURIResource predicate, ARTNode object) {		
		return nodeFact.createStatement(subject, predicate, object);
	}

	// **************************
	// * PREFIX MAPPING METHODS *
	// **************************

	public String expandQName(String qname) throws ModelAccessException {
		// TODO Auto-generated method stub
		return null;
	}

	public String getQName(String uri) throws ModelAccessException {
		// TODO Auto-generated method stub
		return null;
	}

	public Map<String, String> getNamespacePrefixMapping() throws ModelAccessException {
		// TODO Auto-generated method stub
		return null;
	}

	public String getNSForPrefix(String prefix) throws ModelAccessException {
		// TODO Auto-generated method stub
		return null;
	}

	public String getPrefixForNS(String namespace) throws ModelAccessException {
		// TODO Auto-generated method stub
		return null;
	}

	public void setNsPrefix(String namespace, String prefix) throws ModelUpdateException {
		// TODO Auto-generated method stub

	}

	public void removeNsPrefixMapping(String namespace) throws ModelUpdateException {
		// TODO Auto-generated method stub

	}

	private final static String selectQueryHeader = "SELECT ?s ?p ?o ";
	private final static String askQueryHeader = "ASK ";

	private String sparqlPrettyPrint(ARTNode node) {
		if (node.isURIResource())
			return "<" + node.toString() + ">";

		// blank or string
		return node.toString();
	}

	private String prepareTripleAsGraphPattern(ARTResource subject, ARTURIResource predicate, ARTNode object) {
		StringBuffer graphPattern = new StringBuffer("{");
		if (subject.equals(NodeFilters.ANY))
			graphPattern.append("?s ");
		else
			graphPattern.append(sparqlPrettyPrint(subject) + " ");

		if (predicate.equals(NodeFilters.ANY))
			graphPattern.append("?p ");
		else
			graphPattern.append(sparqlPrettyPrint(predicate) + " ");

		if (object.equals(NodeFilters.ANY))
			graphPattern.append("?o }");
		else
			graphPattern.append(sparqlPrettyPrint(object) + " }");

		return graphPattern.toString();
	}

	/**
	 * wraps a {@link TupleBindings} object the content of which is composed of a <subject,predicate,object>
	 * triple by exporting it as an RDF Statement
	 * 
	 * @author Armando Stellato
	 * 
	 */
	public class StatementAfterTupleBindings implements ARTStatement {

		TupleBindings bindings;
		ARTResource subj;
		ARTURIResource pred;
		ARTNode obj;

		public StatementAfterTupleBindings(TupleBindings bindings, ARTResource subj, ARTURIResource pred,
				ARTNode obj) {
			this.bindings = bindings;
			this.subj = subj;
			this.pred = pred;
			this.obj = obj;
		}

		public ARTResource getSubject() {
			if (subj == null)
				return bindings.getBoundValue("s").asResource();
			return subj;
		}

		public ARTURIResource getPredicate() {
			if (pred == null)
				return bindings.getBoundValue("p").asURIResource();
			return pred;
		}

		public ARTNode getObject() {
			if (obj == null)
				return bindings.getBoundValue("o");
			return obj;
		}

		public ARTResource getNamedGraph() {
			throw new IllegalStateException(
					"this method has not yet been implemented in the SPARQL Based RDF Triple Store");
		}

	}

	/**
	 * wraps a {@link TupleBindingsIterator} the content of which is composed of <subject,predicate,object>
	 * triples by exporting this content as an RDFIterator over statements
	 * 
	 * @author Armando Stellato
	 * 
	 */
	public class StatementIteratorAfterTupleBindingsIterator implements ARTStatementIterator {

		TupleBindingsIterator bit;
		ARTResource subj;
		ARTURIResource pred;
		ARTNode obj;

		StatementIteratorAfterTupleBindingsIterator(TupleBindingsIterator bit, ARTResource subj,
				ARTURIResource pred, ARTNode obj) {
			this.bit = bit;

			// the reason for this is that a check for null may be faster with respect to checking equivalence
			// to NodeFilters.ANY. Thus we convert ANY to null just one time during iterator initialization
			// and then check for null on all bindings to improve performances

			if (subj.equals(NodeFilters.ANY))
				this.subj = null;
			else
				this.subj = subj;

			if (pred.equals(NodeFilters.ANY))
				this.pred = null;
			else
				this.pred = pred;

			if (obj.equals(NodeFilters.ANY))
				this.obj = null;
			else
				this.obj = obj;
		}

		public boolean streamOpen() throws ModelAccessException {
			return bit.streamOpen();
		}

		public ARTStatement getNext() throws ModelAccessException {
			return new StatementAfterTupleBindings(bit.getNext(), subj, pred, obj);
		}

		public void close() throws ModelAccessException {
			bit.close();
		}

		public boolean hasNext() {
			return bit.hasNext();
		}

		public ARTStatement next() {
			return new StatementAfterTupleBindings(bit.next(), subj, pred, obj);
		}

		public void remove() {
			bit.remove();
		}

	}

	public Update createUpdate(QueryLanguage ql, String query, String baseURI)
			throws UnsupportedQueryLanguageException, ModelAccessException, MalformedQueryException {
		return tqModel.createUpdate(ql, query, baseURI);
	}



}
